<?php

namespace lc\activity\lottery;

use lc\activity\model\LotteryAward;
use lc\activity\model\LotteryAwardList;
use lc\activity\model\LotteryConfig;
use lc\activity\model\LotteryIps;
use lc\activity\model\LotteryLogs;
use lc\activity\model\LotteryUserExtra;
use lc\activity\RedPackAct;
use think\db\exception\DbException;
use think\facade\Db;

class LotteryAct extends LotteryService
{
    /**
     * @param string 	$appId
     * @param string	$appSecret
     * @param $user
     */
    public function __construct(string $appId, string $appSecret, $user)
    {
        $this->user      = $user;
        $this->appId     = $appId;
        $this->appSecret = $appSecret;
        $this->ip        = request()->ip();
        $this->time      = time();
        $this->today     = strtotime('today');
    }

	/**
	 * @desc    抽奖
	 * @param 	mixed 	$whereCondition
	 * @param 	array	$orderBy
	 * @return 	array
	 * @throws 	DbException
	 * @throws 	\think\db\exception\DataNotFoundException
	 * @throws 	\think\db\exception\ModelNotFoundException
	 */
    public function run($whereCondition = '', $orderBy = ['a_id', 'asc'])
    {
        // 1. 判断活动时间，写入抽奖日志
        $this->ipsRecord(LotteryIps::IP_TYPE_REQUEST);
        $this->lotteryStartOrEnd();
        if ($this->response['code'] !== 0) {
            return $this->response;
        }
        $this->userExtraRecord(LotteryUserExtra::USER_EXTRA_TYPE_REQUEST);

        // 2. 判断用户是否有抽奖次数，并扣除相应抽奖次数
        if ($this->reduceUserCount() === 0) {
            return $this->response;
        }

        // 3. 判断抽奖活动参与次数和参与人数
        if ($this->checkLotteryCount() === 0) {
            $this->responseNotAward();
            return $this->response;
        }

        // 4. 判断用户抽奖参与次数
        if ($this->checkUserLotteryCount() === 0) {
            $this->responseNotAward();
            return $this->response;
        }
        $this->ipsRecord(LotteryIps::IP_TYPE_JOIN);
        $this->userExtraRecord(LotteryUserExtra::USER_EXTRA_TYPE_JOIN);

        // 4. 判断奖池发奖时间
        if ($this->timeIfCanWin() === 0) {
            $this->responseNotAward();
            return $this->response;
        }

        // 6. 判断用户中奖次数
        if ($this->checkUserWinCount() === 0) {
            $this->responseNotAward();
            return $this->response;
        }

        // 7. 判断IP中奖次数
        if ($this->checkIpLimitWinCount() === 0) {
            $this->responseNotAward();
            return $this->response;
        }

        // 8. 列表抽奖
		if ($this->config->l_lottery_type == LotteryConfig::LOTTERY_TYPE_RANDOM) {
			if (rand(0, 10000) > $this->config->l_rand) {
				$this->lotteryLog('随机未中奖');
				$this->response['code'] = LotteryConfig::RANDOM_NOT_WIN;
				$this->responseNotAward();
				return $this->response;
			}
			$winAward = $this->randomLottery($whereCondition, $orderBy);
		} else {
			$winAward = $this->listLottery($whereCondition);
		}

        if (!$winAward) {
            $this->lotteryLog('奖池暂无奖品');
            $this->response['code'] = LotteryConfig::NO_AWARD;
            $this->responseNotAward();
            return $this->response;
        }

        // 9. 更新奖池
		$updateAwardRes = $this->updateAward($this->config->l_lottery_type, $winAward);
        if (!$updateAwardRes) {
            // 更新奖池失败，则不中奖
            $this->lotteryLog('更新奖池失败');
            $this->response['code'] = LotteryConfig::UPDATE_STOCK_FAIL;
            $this->responseNotAward();
            return $this->response;
        }

        // 10. 发奖
		switch ($winAward->a_award_type) {
			case LotteryAward::AWARD_TYPE_RED_PACK_CASH:
			case LotteryAward::AWARD_TYPE_RED_PACK_BALANCE:
				// 企业支付发红包
				$redPackAct = new RedPackAct();
				$redPackAct->setPid($this->config->l_pid);
				$sendAwardRes = $redPackAct->send($this->user->toArray(), $winAward->a_price);
				break;
			case LotteryAward::AWARD_TYPE_NORMAL:
			default:
				// 普通奖品
				$sendAwardRes = ['status' => 1, 'msg' => '发奖成功'];
				break;
		}

        // 10. 抽奖结果
        if ($sendAwardRes['status'] === 1) {
            $this->ipsRecord(LotteryIps::IP_TYPE_WIN);
            $this->userExtraRecord(LotteryUserExtra::USER_EXTRA_TYPE_WIN);

            $this->lotteryLog("抽中奖品：{$winAward->a_awardname}");

            $this->response['code'] = 0;
            $this->response['msg']  = "恭喜抽中奖品: {$winAward->a_awardname}";
            $this->response['data'] = [
				'aId'       => $winAward->id,
				'awardId' 	=> $winAward->a_awardid,
				'awardName' => $winAward->a_awardname,
				'awardCode' => isset($winAward->a_code) ? $winAward->a_code : '',
				'wId'		=> 0,
				'isReceive' => 0
            ];

            return $this->response;
        } else {
            // 发送红包或充值话费等操作失败，则不中奖
			if ($this->config->l_lottery_type == LotteryConfig::LOTTERY_TYPE_RANDOM) {
				LotteryAward::where('a_id', $winAward->a_id)
					->save(['a_uid' => 0, 'a_sendtime' => 0, 'a_ip' => null]);
			} else {
				LotteryAwardList::where('a_id', $winAward->a_id)
					->dec('a_received')
					->save();
				LotteryAward::where('a_id', $winAward->id)->delete();
			}

            $this->lotteryLog($sendAwardRes['msg']);
            $this->response['code'] = LotteryConfig::AWARD_SEND_FAIL;
            $this->responseNotAward();
            return $this->response;
        }
    }

	/**
	 * @desc	随机抽奖
	 * @param 	mixed	$whereCondition	随机条件
	 * @param 	array 	$orderBy		排序值
	 * @return 	array|null|\think\Model
	 * @throws 	DbException
	 * @throws 	\think\db\exception\DataNotFoundException
	 * @throws 	\think\db\exception\ModelNotFoundException
	 */
	private function randomLottery($whereCondition, array $orderBy)
	{
		return LotteryAward::where(function ($query) use ($whereCondition) {
				if (!empty($whereCondition)) {
					$query->where($whereCondition);
				} else {
					$query->where('a_uid', 0);
					switch ($this->config->l_award_type) {
						case 1:
							// 发放当前时段整点时间戳奖品
							$query->where('a_start', strtotime(date('Y-m-d H:00:00')));
							break;
						case 2:
							// 发放当天零点时间戳奖品
							$query->where('a_start', $this->today);
							break;
						case 0:
						default:
							// 奖品累计
							$query->where('a_start', '<=', $this->time);
							break;
					}
					if (!empty($this->config->l_award_id)) {
						$query->where('a_awardid', $this->config->l_award_id);
					}
				}
			})
			->order($orderBy[0], $orderBy[1])
			->find();
	}

    /**
     * @desc    列表抽奖
	 * @param 	mixed	$whereCondition	随机条件
     * @return  array
     * @throws  DbException
     */
    private function listLottery($whereCondition)
    {
        $win    = mt_rand(1, 10000);
        $area   = 0;
        $award  = [];
        $list   = LotteryAwardList::where(function ($query) use ($whereCondition) {
				if ($whereCondition) {
					$query->where($whereCondition);
				} else {
					$query->where("a_state = 1 and a_received + 1 <= a_number");
				}
			})
			->select();
        if (!$list->isEmpty()) {
            foreach ($list as $item) {
                $area += $item->a_rate;
                if ($win <= $area) {
                    $award = $item;
                    break;
                }
            }
        }

        return $award;
    }

    /**
     * @desc 未中奖情况，判断是否有默认奖品
     */
    private function responseNotAward()
    {
        if ($this->response['data']['awardId'] == 0 && $this->config->l_default_award > 0) {
			$updateAwardRes = false;
			if ($this->config->l_lottery_type == LotteryConfig::LOTTERY_TYPE_RANDOM) {
				$winAward = LotteryAward::where(['a_uid' => 0, 'a_awardid' => $this->config->l_default_award])
					->order('a_id', 'desc')
					->find();
			} else {
				$winAward = LotteryAwardList::where("a_received + 1 <= a_number and a_state = 1 and a_awardid = {$this->config->l_default_award}")->find();
			}
			if ($winAward) {
				$updateAwardRes = $this->updateAward($this->config->l_lottery_type, $winAward);
			}

            if ($updateAwardRes) {
                $this->lotteryLog("默认中奖 {$winAward->a_awardname}");

                $this->response['code'] = LotteryConfig::WIN_DEFAULT;
                $this->response['msg']  = "恭喜抽中奖品: {$winAward->a_awardname}";
                $this->response['data'] = [
					'aId'       => $winAward->id,
					'awardId' 	=> $winAward->a_awardid,
					'awardName' => $winAward->a_awardname,
					'awardCode' => isset($winAward->a_code) ? $winAward->a_code : '',
					'wId'		=> 0,
					'isReceive' => 0

                ];
            }
        }
    }

    /**
     * @desc 判断活动是否未开始或已结束
     */
    private function lotteryStartOrEnd()
    {
        $config = LotteryConfig::connect('main')->where(['l_appid' => $this->appId])->find();
        if (!$config) {
            $this->response['code'] = 1000;
            $this->response['msg']  = 'APPID 错误，未匹配到抽奖活动.';
        } elseif ($config->l_secret != $this->appSecret) {
            $this->response['code'] = 1000;
            $this->response['msg']  = 'APPID 与 APP_SECRET 不匹配.';
        } elseif (strtotime($config->l_start_time) > $this->time) {
            $this->response['code'] = 1000;
            $this->response['msg']  = "活动于 {$config['l_start_time']} 正式开始.";
        } elseif (strtotime($config->l_end_time) < $this->time) {
            $this->response['code'] = 1000;
            $this->response['msg']  = "活动于 {$config['l_end_time']} 已结束，敬请期待下次活动.";
        }

        try {
            // 抽奖日志
			$this->log       		= new LotteryLogs();
            $this->log->ll_key     	= $this->user->u_uid . '_' . intval($this->time / 2);
            $this->log->ll_uid     	= $this->user->u_uid;
            $this->log->ll_phone   	= !empty($this->user->u_phone) ? $this->user->u_phone : '';
            $this->log->ll_status  	= '未中奖';
            $this->log->ll_ip      	= $this->ip;
            $this->log->ll_day     	= $this->today;
            $this->log->save();

            $this->config = $config;
        } catch (\Exception $e) {
            $this->response['code'] = LotteryConfig::REQUEST_FREQUENT;
            $this->response['msg']  = '请求频繁，请稍后重试';
        }
    }

    /**
     * @desc    判断中奖时间
     * @return  int
     */
    private function timeIfCanWin()
    {
		$hour 	  	= date('G', $this->time);
		$startNoCan = (!is_null($this->config->l_win_start) && $this->config->l_win_start > 0) && $hour < $this->config->l_win_start;
		$endNoCan   = (!is_null($this->config->l_win_end) && $this->config->l_win_end > 0) && $hour > $this->config->l_win_end;
		if ($startNoCan || $endNoCan) {
			$this->response['code'] = LotteryConfig::TIME_LIMIT;
			$this->lotteryLog('当前时间不允许中奖');
			return 0;
		}
		return 1;
    }

    /**
     * @desc    判断用户是否有抽奖次数，并减少相应次数
     * @return  int
     */
    private function reduceUserCount()
    {
        $field      = $this->config->l_field;
        $fieldVal   = $this->config->l_field_val;
        $count      = $this->user->$field;
        // 处理用户的抽奖资格比如，扣除积分，减少抽奖次数之类的操作
        if (($fieldVal > 0 && $count >= $fieldVal) || ($fieldVal == 0 && $count > $fieldVal)) {
            switch ($fieldVal) {
                case 0:
                    // l_field_val默认值为1，如果这个值为0，则需要特殊处理，分享多次只能抽奖1次之类的情况
                    $newVal = 0;
                    break;
                case 1:
                default:
                    $newVal = $count - $fieldVal;
                    break;
            }
            $this->user->$field = $newVal;
            if ($this->user->save()) {
                return 1;
            } else {
                $this->lotteryLog('扣除用户抽奖次数失败');
                $this->response['code'] = LotteryConfig::REDUCE_USER_COUNT_FAIL;
                return 0;
            }
        } else {
            $this->lotteryLog('暂无抽奖次数');
            $this->response['code'] = LotteryConfig::USER_NO_COUNT;
			$this->response['msg']  = '抽奖次数用完了.';
            return 0;
        }
    }

    /**
     * @desc 	判断当前用户当天或活动期间参与抽奖次数，并写入抽奖日志
     * @return 	int
     */
    private function checkUserLotteryCount()
    {
        if ($this->userExtra->ue_last_day == $this->today) {
            if ($this->config->l_daily_count > 0 && $this->userExtra->ue_day_join_count >= $this->config->l_daily_count) {
                $this->response['code'] = LotteryConfig::USER_TODAY_JOIN_LIMIT;
                $this->lotteryLog("今日参与次数已满，最大次数 {$this->config->l_daily_count}");
                return 0;
            }
        }
        if ($this->config->l_max_count > 0 && $this->userExtra->ue_max_join_count >= $this->config->l_max_count) {
            $this->response['code'] = LotteryConfig::USER_MAX_JOIN_LIMIT;
            $this->lotteryLog("活动期间参与次数已满，最大次数 {$this->config->l_max_count}");
            return 0;
        }
        return 1;
    }

    /**
     * @desc    判断用户今日或活动期间中奖次数
     * @return  int
     */
    private function checkUserWinCount()
    {
        if ($this->userExtra->ue_last_day == $this->today) {
            // 判断今日最大中奖次数
            if ($this->config->l_day_win_count > 0 && $this->userExtra->ue_day_win_count >= $this->config->l_day_win_count) {
                $this->response['code'] = LotteryConfig::USER_TODAY_WIN_LIMIT;
                $this->lotteryLog("当日中奖次数 {$this->config->l_day_win_count} 已满，无法再次中奖");
                return 0;
            }
        }
        // 判断活动期间最大中奖次数
        if ($this->config->l_max_win_count > 0 && $this->userExtra->ue_max_win_count >= $this->config->l_max_win_count) {
            $this->response['code'] = LotteryConfig::USER_MAX_WIN_LIMIT;
            $this->lotteryLog("活动期间中奖次数 {$this->config->l_max_win_count} 已满，无法再次中奖");
            return 0;
        }

        return 1;
    }

    /**
     * @desc    判断今日或活动期间IP中奖限制
     * @return  int
     */
    private function checkIpLimitWinCount()
    {
		// 判断今日最大中奖次数
		if ($this->config->l_daily_ip_limit > 0 && $this->ipRecord->i_day_win_count >= $this->config->l_daily_ip_limit) {
			$this->response['code'] = LotteryConfig::IP_TODAY_WIN_LIMIT;
			$this->lotteryLog("该IP {$this->ip}，当日中奖次数 {$this->config['l_daily_ip_limit']} 已满，无法再次中奖");
			return 0;
		}
		// 判断活动期间最大中奖次数
		if ($this->config->l_max_ip_limit > 0 && $this->ipRecord->i_max_win_count >= $this->config->l_max_ip_limit) {
			$this->response['code'] = LotteryConfig::IP_MAX_WIN_LIMIT;
			$this->lotteryLog("该IP {$this->ip}，活动期间中奖次数 {$this->config['l_max_ip_limit']} 已满，无法再次中奖");
			return 0;
		}
		return 1;
    }

    /**
     * @desc    判断活动参与次数或人数
     * @return  int
     */
    private function checkLotteryCount()
    {
        // 判断最大参与次数
        if ($this->isNewUser && $this->config->l_max_join_user > 0 && $this->config->l_curr_join_user > $this->config->l_max_join_user) {
            $this->response['code'] = LotteryConfig::MAX_JOIN_USER_LIMIT;
            $this->lotteryLog("最大抽奖用户数 {$this->config->l_max_join_user} 已满，无法再次中奖");
            return 0;
        }
        // 判断最大参与人数
        if ($this->config->l_max_join_count > 0 && $this->config->l_curr_join_count >= $this->config->l_max_join_count) {
            $this->response['code'] = LotteryConfig::MAX_JOIN_COUNT_LIMIT;
            $this->lotteryLog("最大抽奖次数 {$this->config->l_max_join_count} 已满，无法再次中奖");
            return 0;
        }

        return 1;
    }

    /**
     * @param   int $type
     * @return  int
     * @throws  DbException
     * @throws  \think\db\exception\DataNotFoundException
     * @throws  \think\db\exception\ModelNotFoundException
     */
    private function ipsRecord(int $type)
    {
        if ($type == LotteryIps::IP_TYPE_JOIN) {
			$this->ipRecord->save([
				'i_day_join_count' => Db::raw('i_day_join_count + 1'),
				'i_max_join_count' => Db::raw('i_max_join_count + 1')
			]);
			return 1;
        } elseif ($type == LotteryIps::IP_TYPE_WIN) {
			$this->ipRecord->save([
				'i_day_win_count' => Db::raw('i_day_win_count + 1'),
				'i_max_win_count' => Db::raw('i_max_win_count + 1')
			]);
			return 1;
        } else {
            $ipExist = LotteryIps::where('i_ip', $this->ip)->find();
            if (!$ipExist) {
                $this->ipRecord = new LotteryIps();
                $data = [
                    'i_ip'              => $this->ip,
                    'i_day_req_count'   => 1,
                    'i_max_req_count'   => 1,
                    'i_day_join_count'  => 0,
                    'i_max_join_count'  => 0,
                    'i_day_win_count'   => 0,
                    'i_max_win_count'   => 0,
                    'i_last_day'        => $this->today,
                ];
                $this->ipRecord->save($data);
                return 1;
            } else {
                $this->ipRecord = $ipExist;
                if ($ipExist->i_last_day != $this->today) {
                    // 更新为今日数据
                    $update = [
                        'i_last_day'        => $this->today,
                        'i_day_req_count'   => 1,
                        'i_max_req_count'   => Db::raw('i_max_req_count + 1'),
                        'i_day_join_count'  => 0,
                        'i_day_win_count'   => 0
                    ];
                    $this->ipRecord->i_day_win_count = 0;
                } else {
                    $update['i_day_req_count'] = Db::raw('i_day_req_count + 1');
                    $update['i_max_req_count'] = Db::raw('i_max_req_count + 1');
                }
				$this->ipRecord->save($update);
				return 1;
            }
        }
    }

    /**
     * @desc    记录用户参与，中奖
     * @param   int $type
     * @return  int
     * @throws  DbException
     * @throws  \think\db\exception\DataNotFoundException
     * @throws  \think\db\exception\ModelNotFoundException
     */
    private function userExtraRecord(int $type)
    {
        if ($type == LotteryUserExtra::USER_EXTRA_TYPE_JOIN) {
            $update['ue_day_join_count'] = Db::raw('ue_day_join_count + 1');
            $update['ue_max_join_count'] = Db::raw('ue_max_join_count + 1');
            $update['ue_last_day']       = $this->today;
			$this->userExtra->save($update);
			return 1;
        } elseif ($type == LotteryUserExtra::USER_EXTRA_TYPE_WIN) {
            $update['ue_day_win_count'] = Db::raw('ue_day_win_count + 1');
            $update['ue_max_win_count'] = Db::raw('ue_max_win_count + 1');
            $update['ue_last_day'] 		= $this->today;
			$this->userExtra->save($update);
			return 1;
        } else {
            // 用户统计
            $extraExist = LotteryUserExtra::where('ue_uid', $this->user->u_uid)->find();
            if (!$extraExist) {
                $this->userExtra = new LotteryUserExtra();
                $data = [
                    'ue_uid'            => $this->user->u_uid,
                    'ue_last_day'       => $this->today,
                    'ue_day_join_count' => 0,
                    'ue_max_join_count' => 0,
                    'ue_day_win_count'  => 0,
                    'ue_max_win_count'  => 0,
                ];
                $this->userExtra->save($data);

                LotteryConfig::connect('main')
                    ->where('l_id', $this->config->l_id)
                    ->inc('l_curr_join_count')
                    ->inc('l_curr_join_user')
                    ->save();
                $this->config->l_curr_join_user  += 1;
                $this->config->l_curr_join_count += 1;
                $this->isNewUser = 1;
                return 1;
            } else {
                $this->userExtra = $extraExist;
                if ($extraExist->ue_last_day != $this->today) {
                    $update['ue_day_join_count'] = 0;
                    $update['ue_day_win_count']  = 0;
                    $update['ue_last_day']       = $this->today;

					$this->userExtra->save($update);
                }
				LotteryConfig::connect('main')->where('l_id', $this->config->l_id)->inc('l_curr_join_count')->save();
                $this->config->l_curr_join_count += 1;
				return 1;
            }
        }
    }

    /**
     * @desc    修改对应抽奖 log 的 status 状态，用于记录用户的每次抽奖结果
     * @param   string $status 抽奖结果状态
     */
    private function lotteryLog($status)
    {
        $this->log->ll_status = $status;
        $this->log->save();
    }

    /**
     * @desc    更新奖池
     * @param   int 	$type 抽奖类型
     * @param   $award 	奖品
     * @return  mixed
     */
    private function updateAward(int $type, &$award)
    {
		if ($type == LotteryConfig::LOTTERY_TYPE_RANDOM) {
			$award->id = $award->a_id;
			return LotteryAward::where(['a_uid' => 0, 'a_id' => $award->a_id])
				->save([
					'a_uid'      => $this->user->u_uid,
					'a_sendtime' => $this->time,
					'a_ip'       => $this->ip
				]);
		} else {
			$updateRes = LotteryAwardList::where("a_received + 1 <= a_number and a_id = {$award->a_id}")
				->inc('a_received', 1)
				->save();
			if ($updateRes) {
				$award->a_code 		= md5(uniqid() . rand(100, 9999999));
				$award->a_exchange 	= ($award->a_need_check == 1) ? 0 : 1;
				$insertAward 		= LotteryAward::create([
					'a_awardid'     => $award->a_awardid,
					'a_award_type'  => $award->a_award_type,
					'a_awardname'   => $award->a_awardname,
					'a_price'       => $award->a_price,
					'a_start'       => $this->today,
					'a_code'        => $award->a_code,
					'a_uid'         => $this->user->u_uid,
					'a_sendtime'    => $this->time,
					'a_ip'          => $this->ip,
					'a_exchange'	=> $award->a_exchange
				]);
				$award->id = $insertAward->a_id;
			}
			return $updateRes;
		}
    }
}